
import unittest
import copy
import random

class TestContainerCopy(unittest.TestCase):
    # ============================================================ #
    # shallow copy
    # ============================================================ #
    def check_shallow_copy(self,copy_func):
        """
        shallow copy is just create a totally new instance, but its member are just referencing the same member as the original instance
        (that is to say, two different container, but items hold are the same,
        then for immutable item, modify immutable item through copy container, will let that immutable item point to another new instance,
        but for mutable item, modify mutable item through one name, will affect the other)
        
        there are three ways to do shallow copy:
        1. fully slice: cpylist = srclist[:]
        2. using constructor: cpylist = list(srclist) 
        3. using copy.copy
        """
        # --------------------------------- make and copy original
        srclist = [1,"cheka",[2,"stasi"]]
        cpylist = copy_func(srclist)
        
        # --------------------------------- check equal
        self.assertEqual(srclist,cpylist)
        
        # --------------------------------- check container itself totally new
        self.assertTrue(srclist is not cpylist)
    
        # --------------------------------- check items in the container referencing the same object
        size = len(srclist)
        for index in xrange(size):
            self.assertTrue(srclist[index] is cpylist[index])
            
        # --------------------------------- modify immutable item, resulting in pointing to new object
        cpylist[1] = None
        self.assertTrue(cpylist[1] is not srclist[1])
        
        # --------------------------------- modify mutable item, affect original ones
        new_number = 100
        cpylist[2][0] = new_number
        self.assertEqual(new_number,srclist[2][0])
        
        
    def test_shallowcopy_by_slice(self):
        self.check_shallow_copy(lambda x: x[:])
        
    def test_shallowcopy_by_constructor(self):
        self.check_shallow_copy(lambda x: list(x))
        
    def test_shallowcopy_by_copy(self):
        self.check_shallow_copy(lambda x: copy.copy(x))
    
    # ============================================================ #
    # deep copy
    # ============================================================ #
    def test_deep_copy(self):
        """
        when a container is deep-copied, not only the container itself are copied
        but the mutable items in the container is also copied, but the immutable items are not copied nor necessary
        """
        srclist = [1,"cheka",[2,"stasi"]]
        cpylist = copy.deepcopy(srclist)
        
        # --------------------------------- test equal
        self.assertEqual(srclist,cpylist)
        
        # --------------------------------- test not same
        self.assertTrue(srclist is not cpylist)
        
        # --------------------------------- test items same or not
        self.assertTrue(srclist[0] is cpylist[0])
        self.assertTrue(srclist[1] is cpylist[1])
        self.assertTrue(srclist[2] is not cpylist[2])


class TestObjectCopy(unittest.TestCase):
    # ============================== helper
    class Manager(object):
        def __init__(self,name):
            self._name = name
            self._workers = []
            
        def add_worker(self,worker):
            self._workers.append(worker)
            
        def get_worker(self,index):
            return self._workers[index]
            
    class Worker(object):
        def __init__(self,name):
            self._name = name
            
        def __eq__(self,other):
            if (not isinstance(other,TestObjectCopy.Worker)):
                return False
            elif self is other:
                return True
            else :
                return self._name == other._name

    def make_manager(num_workers):
        mgr = TestObjectCopy.Manager("cheka")
        
        for index in xrange(num_workers):
            mgr.add_worker(TestObjectCopy.Worker("worker-%d"%(index + 1)))
            
        return mgr
    make_manager = staticmethod(make_manager)
            
    # ============================== test
    def test_deep_copy(self):
        num_workers = 6
        srcmgr = TestObjectCopy.make_manager(num_workers)
        cpymgr = copy.deepcopy(srcmgr)
        
        # -------------------- immutable member field not copied
        self.assertTrue(srcmgr._name,cpymgr._name)
            
        # -------------------- mutable member field are copied
        self.assertTrue(srcmgr._workers is not cpymgr._workers)
        
        # !!!!!!!!!!!!!!!!!!!! deep copy is recursive, not only mutable member field is copied
        # !!!!!!!!!!!!!!!!!!!! but also "mutable member of that mutable member field" is also copied
        for index in xrange(num_workers):
            srcworker = srcmgr.get_worker(index)
            cpyworker = cpymgr.get_worker(index)
            
            self.assertEqual(srcworker,cpyworker)
            self.assertTrue(srcmgr.get_worker(index) is not cpymgr.get_worker(index))

if __name__ == "__main__":
    unittest.main()